package com.iguo.tube.controller;


import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.iguo.tube.dao.PointMapper;
import com.iguo.tube.form.Edge;
import com.iguo.tube.model.Point;
import com.iguo.tube.service.PointService;
import com.iguo.tube.util.HaversineDistance;
import com.iguo.tube.util.RedisCacheUtil;
import com.iguo.tube.util.Result;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.*;
import org.springframework.data.redis.core.RedisTemplate;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author iGuo
 * @since 2024-09-28
 */
@RestController
@RequestMapping("/point")
public class PointController {
    @Autowired
    private PointService pointService;
    @Autowired
    private PointMapper pointMapper;
    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisCacheUtil redisCacheUtil;

    @Value("${upload.cache}")
    String filePath;
    @Value("${upload.time}")
    Integer cacheTime;

    private static final String LIST_KEY = "myList";
    @CrossOrigin
    @GetMapping("/getPoint")
    public Result findPoint(){
        QueryWrapper<Point> wrapper = new QueryWrapper<>();
        wrapper.eq("state",1);
        List<Point> list = pointMapper.selectList(wrapper);
        return Result.ok().put("point",list);
    }

    @CrossOrigin
    @GetMapping("/getAllPoint")
    public Result findAllPoint(){

        QueryWrapper<Point> wrapper = new QueryWrapper<>();
        List<Point> list = pointMapper.selectList(wrapper);
        return Result.ok().put("point",list);
    }
    @CrossOrigin
    @PostMapping("/addPoint")
    public Result addPoint(@RequestBody Point point){
        point.setState(1);
        point.setPriority(999);
        return Result.ok().put("status",pointMapper.insert(point));
    }
    @CrossOrigin
    @PutMapping("/changeState")
    public Result changeState(@RequestParam Integer id, @RequestParam Integer state){
        //System.out.println(id);
        if (id == null || state == null) {
            return Result.error("ID和状态不能为空");
        }
        QueryWrapper<Point> wrapper = new QueryWrapper<>();
        wrapper.eq("id", id);
        Point point = pointMapper.selectOne(wrapper);
        if (point == null) {
            return Result.error("未找到对应的点");
        }
        // 更新状态
        point.setState(state);
        int updatedRows = pointMapper.updateById(point);
        if (updatedRows > 0) {
            return Result.ok();
        } else {
            return Result.error("状态更新失败");
        }
    }
    @CrossOrigin
    @GetMapping("/getLine")
    public Result findLine() {
        //Kruskal算法适用于边数较少的场景
        //Prim算法适用于边数较多的场景
        //因为这里是一个完全图，节点两两都相连，因此选择Prim算法



        //1.先拿到所有要进行运算的点位的经纬度
        QueryWrapper<Point> wrapper = new QueryWrapper<>();
        wrapper.eq("state", 1)
                .orderByAsc("priority");
        List<Point> list = pointMapper.selectList(wrapper);


        //用于存储记录
        List<HashMap<String,Object>> record = new ArrayList<>();
        record.add(new HashMap<String,Object>());
        record.get(0).put("point",list);


        //2.获取两两点位之间的距离，使用哈弗辛公式
        //adjacencyList.get(i).get(j).distance
        //代表 点位 i 到点位 i+j+1的 距离
        List<Edge> adjacencyList = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            for (int j = i + 1; j < list.size(); j++) {
                int distance = HaversineDistance.haversine(list.get(i), list.get(j));
                Edge edge = new Edge(list.get(i), list.get(j), distance);
                //System.out.println(edge);
                adjacencyList.add(edge);
            }
        }

        //3.开搞
        //最小堆，优先级队列，按距离从小到大排
        PriorityQueue<Edge> minHeap = new PriorityQueue<>(Comparator.comparingInt(Edge::getDistance));
        //这里面存最小生成树的边
        List<Edge> mst = new ArrayList<>();

        Edge edge = new Edge(list.get(0), list.get(0), 0);
        //将起始顶点的边加入最小堆
        minHeap.offer(edge);


        while (!minHeap.isEmpty()&&mst.size() < list.size()-1) {
            //把距离最小的拿出来
            Edge edge1 = minHeap.poll();
            Point point = edge1.getToPoint();
            int distance = edge1.getDistance();

            HashMap<String,Object> map = new HashMap<>();
            map.put("polledPoint",point);
            map.put("flag",1);
            record.add(map);


            //被用了
            if(point.getState() == 2) continue;
            point.setState(2);

            //添加到最小生成树中
            if(distance!=0){
                mst.add(edge1);
            }
            //遍历与刚选的edge1相邻的边
            for(Edge edge2:adjacencyList){
                //放入
                if(edge2.getFromPoint().equals(point)){
                    minHeap.offer(edge2);
                } else if (edge2.getToPoint().equals(point)) {
                    Edge edge3 = new Edge(edge2.getToPoint(),edge2.getFromPoint(),edge2.getDistance());
                    minHeap.offer(edge3);
                }
            }
            //把这次的优先队列存入记录
            HashMap<String,Object> resultMap = new HashMap<>();
            //深拷贝minHeap
            List<Edge> minHeapCopy = deepCopyPriorityQueueToList(minHeap);
            resultMap.put("waitingEdge",minHeapCopy);
            resultMap.put("flag",0);
            record.add(resultMap);

        }
        String uuid = IdUtil.simpleUUID();
        //再开一个线程，做日志生成的工作，就不耽误主线程返回了
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
              try {
                log(record, uuid);
              } catch (IOException | InterruptedException e) {
                throw new RuntimeException(e);
              }
            }
        });
        thread.start();
        return Result.ok().put("mst",mst).put("uuid",uuid);
    }

    public Result log(List<HashMap<String,Object>> record,String uuid) throws IOException, InterruptedException {
        //存半个小时
        //this.redisTemplate.opsForValue().set(uuid,records,1800, TimeUnit.SECONDS);
        /*
        * System.out.println("-----------------------获取信息生成表格代码-------------------------------");
        * */
        //List<HashMap<String,Object>> record = (List<HashMap<String, Object>>) this.redisTemplate.opsForValue().get(uuid);


        //SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
        //Date = new Date(System.currentTimeMillis());
        //System.out.println(dateFormat.format("开始造表格"+date));



        //导出位置
        //String filePath = "E:\\Desktop\\tubeRecord";

        // 确保目录存在
        File directory = new File(filePath);
        if (!directory.exists()) {
            directory.mkdirs(); // 创建目录
        }
        // 创建表格
        Workbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet("流程表");


        List<Point> pointList = (List<Point>) record.get(0).get("point");
        /*
        * 前面距离表
        * */
        // 创建第一行

        Thread thread = new Thread(() -> {
            Row row = sheet.createRow(0);
            // 合并第一行的第1，2，3列
            sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(0, 0, 0, 2));
            Cell cell = row.createCell(0);
            LocalDateTime now = LocalDateTime.now();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            cell.setCellValue(now.format(formatter));
            CellStyle cellStyle = workbook.createCellStyle();
            cellStyle.setAlignment(HorizontalAlignment.CENTER);
            //第二行放参与运算的点位名
            Row PointNameRow = sheet.createRow(1);

            PointNameRow.createCell(0).setCellValue("点位名:");
            for(int i=0;i<pointList.size();i++){
                PointNameRow.createCell(i+1).setCellValue(pointList.get(i).getName());
            }
            //第3行 - 第2+point数行放距离数据
            for(int i=0;i<pointList.size();i++){
                Row distanceRow = sheet.createRow(2+i);
                distanceRow.createCell(0).setCellValue(pointList.get(i).getName());
                for(int j=0;j<pointList.size();j++){
                    distanceRow.createCell(j+1).setCellValue(HaversineDistance.haversine(pointList.get(i),pointList.get(j)));
                }
            }
        });

        thread.start();


        ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        //List<String> points = new ArrayList<>();
        //生成流程
        for(int index=1;index<record.size();index++){
            final int i = index;
            executor.submit(() -> {
                //获取当前记录，看看拿到的是 取出点位操作还是取出边操作
                HashMap<String,Object> map = record.get(i);
                if(map.get("flag").equals(0)){
                    List<Edge> edgeList = (List<Edge>) map.get("waitingEdge");
                    //取出边操作
                    Row row1 = sheet.createRow(1+pointList.size()+2*i);
                    row1.createCell(0).setCellValue("操作：");
                    row1.createCell(1).setCellValue("边入队");
                    Row row2 = sheet.createRow(2+pointList.size()+2*i);
                    row2.createCell(0).setCellValue("队列中:");
                    for(int j = 0;j<edgeList.size()&&j<100;j++){
                        sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress
                                (2+pointList.size()+2*i, 2+pointList.size()+2*i, 1+3*j, 3+3*j));
                        row2.createCell(3*j+1).setCellValue(edgeList.get(j).getFromPoint().getName()+" -> "+edgeList.get(j).getToPoint().getName()+" "+edgeList.get(j).getDistance());
                    }
                } else if (map.get("flag").equals(1)) {
                    Point point = (Point) map.get("polledPoint");
                    //取出边操作
                    Row row1 = sheet.createRow(1+pointList.size()+2*i);
                    row1.createCell(0).setCellValue("操作：");
                    row1.createCell(1).setCellValue("判断点位");
                    Row row2 = sheet.createRow(2+pointList.size()+2*i);
                    row2.createCell(0).setCellValue("点位名:");
                    row2.createCell(1).setCellValue(point.getName());
                    HashMap<String,Object> next = record.get(i+1);
                    if(next.get("flag").equals(0)){
                        row2.createCell(2).setCellValue("符合要求");
                        row2.createCell(3).setCellValue("刷新队列");
                        //points.add(point.getName());
                        //row2.createCell(5).setCellValue("已连接点位:");
                        //for(int j=0;j<points.size();j++){
                        //    row2.createCell(6+j).setCellValue(points.get(j));
                        //}
                    }else{
                        row2.createCell(2).setCellValue("false");
                    }
                }
            });
        }
        executor.shutdown();
        try {
            if (!executor.awaitTermination(300, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }

        thread.join();


        //生成fileName
        //获取当前时间
        LocalDateTime now = LocalDateTime.now();
        //定义日期格式（24小时制）
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
        // 格式化当前时间
        String formattedDateTime = now.format(formatter);
        // 生成文件名并保存 Excel 文件,这里使用时间戳作为Excel表格的名称防止重名
        String fileName = formattedDateTime+ " Prim" + ".xls";
        File exportFile = new File(directory, fileName);


        //写入
        try (FileOutputStream fos = new FileOutputStream(exportFile)) {
            workbook.write(fos);
        } finally {
            workbook.close();
        }

        //Date date1 = new Date(System.currentTimeMillis());
        //System.out.println(dateFormat.format("表格已造好"+date1));
        //System.out.println(111);
        //将该Filename存储到Redis
        ListOperations<String, String> listOps = redisTemplate.opsForList();
        listOps.rightPush(LIST_KEY, fileName);
        ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
        valueOps.set(fileName, fileName, cacheTime*60, TimeUnit.SECONDS);
        redisTemplate.expire(LIST_KEY, cacheTime*60, TimeUnit.SECONDS);

        ListOperations<String, String> test = redisTemplate.opsForList();
        ValueOperations<String, String> values = redisTemplate.opsForValue();
        Long size = test.size(LIST_KEY);
        List<String> fileList = new ArrayList<>();
        for (long i = 0; i < size; i++) {
            String element = test.index(LIST_KEY, i);
            if (values.get(element) != null) {
                // 如果值存在，添加
                //System.out.println(element);
                fileList.add(element);
            } else {
                // 如果值不存在，移除该元素
                test.remove(LIST_KEY, 1, element); // 移除一个匹配的元素
                //System.out.println("Removed element: " + element);
                i--; // 当前索引减一，避免跳过下一个元素
                size--; // 列表大小减一，确保遍历正确
            }
        }
        return Result.ok();
    }


    //获取所有的FileNames
    @CrossOrigin
    @GetMapping("/getFileNames")
    public Result getFileName(){
        ListOperations<String, String> test = redisTemplate.opsForList();
        ValueOperations<String, String> values = redisTemplate.opsForValue();
        Long size = test.size(LIST_KEY);
        List<String> fileList = new ArrayList<>();
        for (long i = 0; i < size; i++) {
            String element = test.index(LIST_KEY, i);
            if (values.get(element) != null) {
                fileList.add(element);
            } else {
                // 如果值不存在，移除该元素
                test.remove(LIST_KEY, 1, element); // 移除一个匹配的元素
                //System.out.println("Removed element: " + element);
                i--; // 当前索引减一，避免跳过下一个元素
                size--; // 列表大小减一，确保遍历正确
            }
        }
        return Result.ok().put("fileNames",fileList);
    }

    @CrossOrigin
    @GetMapping("/getLog/{fileName}")
    public void downloadExcel(@PathVariable String fileName, HttpServletResponse response) throws IOException {
        //System.out.println(111);
        // 文件路径
        String orderFilePath = filePath +"\\"+ fileName;
        File file = new File(orderFilePath);
        System.out.println(orderFilePath);
        //判断Excel表是否存在
        if (!file.exists()) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND, "没有该文件");
            return;
        }

        // 设置响应头
        response.setContentType("application/vnd.ms-excel");
        response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");

        // 将文件写入响应
        try (FileInputStream fis = new FileInputStream(file);
             OutputStream os = response.getOutputStream()) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            // -1表示读取完啦
            while ((bytesRead = fis.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
        }
    }


    /*1112
    * 对优先队列的深拷贝
    * */
    private static List<Edge> deepCopyPriorityQueueToList(PriorityQueue<Edge> original) {
        List<Edge> copy = new ArrayList<>(original.size());
        for (Edge edge : original) {
            copy.add(edge.deepCopy());
        }
        return copy;
    }

    @CrossOrigin
    @GetMapping("/getLineK")
    public Result findLineKruskal() {

        // 1. 先拿到所有要进行运算的点位的经纬度
        QueryWrapper<Point> wrapper = new QueryWrapper<>();
        wrapper.eq("state", 1)
                .orderByAsc("priority");

        List<Point> list = pointMapper.selectList(wrapper);

        // 2. 获取两两点位之间的距离，使用哈弗辛公式
        List<Edge> edges = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            for (int j = i + 1; j < list.size(); j++) {
                int distance = HaversineDistance.haversine(list.get(i), list.get(j));
                Edge edge = new Edge(list.get(i), list.get(j), distance);
                edges.add(edge);
            }
        }

        // 3. 按照边的权重排序
        edges.sort(Comparator.comparingInt(Edge::getDistance));

        // 4. 并查集初始化
        UnionFind uf = new UnionFind(list.size());
        List<Edge> mst = new ArrayList<>();

        // 5. 遍历所有边，构建最小生成树
        for (Edge edge : edges) {
            int from = list.indexOf(edge.getFromPoint());
            int to = list.indexOf(edge.getToPoint());

            // 检查这条边是否会形成环路
            if (uf.find(from) != uf.find(to)) {
                uf.union(from, to);
                mst.add(edge); // 加入最小生成树
            }
        }

        return Result.ok().put("mst",mst);
    }

    // 并查集实现
    class UnionFind {
        private int[] parent;
        private int[] rank;

        public UnionFind(int size) {
            parent = new int[size];
            rank = new int[size];
            for (int i = 0; i < size; i++) {
                parent[i] = i; // 每个节点的父节点初始化为自己
                rank[i] = 0;   // 初始化秩为 0
            }
        }

        public int find(int p) {
            if (parent[p] != p) {
                parent[p] = find(parent[p]); // 路径压缩
            }
            return parent[p];
        }

        public void union(int p, int q) {
            int rootP = find(p);
            int rootQ = find(q);

            if (rootP != rootQ) {
                // 根据秩进行合并
                if (rank[rootP] > rank[rootQ]) {
                    parent[rootQ] = rootP;
                } else if (rank[rootP] < rank[rootQ]) {
                    parent[rootP] = rootQ;
                } else {
                    parent[rootQ] = rootP;
                    rank[rootP]++;
                }
            }
        }
    }
}